using System;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Research.Kinect.Nui;
using System.Collections.Generic;
using System.Diagnostics;
using KinectGestureRecognizer;
using KinectGestureRecognizer.Gestures;
using KinectGestureRecognizer.HUD;
using KinectGestureRecognizer.Helper;


namespace testKinect
{

    public class Game1 : Microsoft.Xna.Framework.Game
    {

        #region Private Fields

        kinectController kinect;
        Model box;
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        SpriteFont font, bigFont;
        Color rightHandColor, leftHandColor, headColor = Color.White;
        bool leftHandYEAH, rightHandYEAH, bothHandsYEAH, oldBothHandYEAH, oldRightYEAH;
        Texture2D disco, dot;
        float zeroRotationX, zeroRotationY, zeroRotationZ;
        double rotationX, rotationY, rotationZ;
        double originalRotationX, originalRotationY, originalRotationZ;
        float deltaRotationX, deltaRotationY, deltaRotationZ;
        Matrix View, World, Projection;
        Vector3 zeroPosition, originalPosition;

        RasterizerState rasterizerState;
        BlendState blendState;
        DepthStencilState deptStencilState;

        List<AnimatedTextAlert> animatedTextAlerts;
        List<String> textAlertsToAdd;

        #endregion

        #region Constructor

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            kinect = new kinectController(this);
            Components.Add(kinect);
            kinect.gestureDetected += new kinectController.gestureType(kinect_gestureDetected);

            this.animatedTextAlerts = new List<AnimatedTextAlert>();
            this.textAlertsToAdd = new List<String>();

            this.IsMouseVisible = true;

            rasterizerState = new RasterizerState();
            rasterizerState.CullMode = CullMode.None;
            blendState = new BlendState();
            blendState = BlendState.AlphaBlend;
            deptStencilState = new DepthStencilState();
            deptStencilState.DepthBufferEnable = true;

            //graphics.ToggleFullScreen();
        }

        #endregion

        #region Initialize, LoadContent and UnloadContent

        protected override void Initialize()
        {
            Components.Add(new FrameRateCounter(this, kinect));
            kinect.Initialize();

            View = Matrix.CreateLookAt(new Vector3(0, 0, 2), Vector3.Zero, Vector3.Up);
            Projection = Matrix.CreatePerspectiveFieldOfView((float)Math.PI / 2, (float)GraphicsDevice.Viewport.Width / GraphicsDevice.Viewport.Height, 1.0f, 100.0f);
            World = Matrix.Identity;

            base.Initialize();
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            font = Content.Load<SpriteFont>("Font");
            bigFont = Content.Load<SpriteFont>("BigFont");
            
            disco = Content.Load<Texture2D>("textures/disco");
            dot = Content.Load<Texture2D>("textures/redDot");
            box = Content.Load<Model>("rubiks_cube");

            BasicEffect effect = box.Meshes[0].Effects[0] as BasicEffect;
            effect.EnableDefaultLighting();
        }

        protected override void UnloadContent()
        {
            kinect.Uninitialize();
        }

        #endregion

        #region Update & Draw
        
        protected override void Update(GameTime gameTime)
        {
            if (Keyboard.GetState().IsKeyDown(Keys.Escape))
                this.Exit();

            for (int i = 0; i < textAlertsToAdd.Count; i++)
            {
                animatedTextAlerts.Add(new AnimatedTextAlert(gameTime, new TimeSpan(0, 0, 2), textAlertsToAdd[i]));
                textAlertsToAdd.RemoveAt(i);
            }

            for (int i = 0; i < animatedTextAlerts.Count; i++)
            {
                if (animatedTextAlerts[i].shouldBeDisposed)
                    animatedTextAlerts.RemoveAt(i);
                else
                    animatedTextAlerts[i].Update(gameTime);
            }

            animateCube.Update();
            
            controlHandsZ();

            //bothHandsYEAH = leftHandYEAH & rightHandYEAH;

            //if (bothHandsYEAH && !oldBothHandYEAH)
            //{
            //    originalRotationX = rotationX;
            //    zeroRotationX = helperFunctions.angleBetween2Points(kinect.leftHandWorldPosition, kinect.rightHandWorldPosition, 0);
            //    oldBothHandYEAH = true;
            //}
            //else if (bothHandsYEAH && oldBothHandYEAH)
            //{
            //    deltaRotationX = helperFunctions.angleBetween2Points(kinect.leftHandWorldPosition, kinect.rightHandWorldPosition, 0) - zeroRotationX;
            //    rotationX = originalRotationX + deltaRotationX;
            //}
            //else if (!bothHandsYEAH && oldBothHandYEAH)
            //{
            //    oldBothHandYEAH = false;
            //}

            //if (rightHandYEAH && !leftHandYEAH && !oldRightYEAH)
            //{
            //    originalPosition = kinect.rightHandWorldPosition;
            //    oldRightYEAH = true;
            //}
            //else if (rightHandYEAH && !leftHandYEAH && oldRightYEAH)
            //{
            //    deltaRotationY = originalPosition.Y - kinect.rightHandWorldPosition.Y;
            //    rotationY = originalPosition.Y + deltaRotationY;
            //}
            //else if (!rightHandYEAH && !leftHandYEAH && oldRightYEAH)
            //    oldRightYEAH = false;


            //Quaternion rotZ = Quaternion.CreateFromAxisAngle(Vector3.Forward, (float)-rotationX);
            //Quaternion rotX = Quaternion.CreateFromAxisAngle(Vector3.Left, (float)-rotationY);

            //World = Matrix.CreateFromQuaternion(rotZ) * Matrix.CreateFromQuaternion(rotX);

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            resetState();

            Matrix[] transforms = new Matrix[box.Bones.Count];
            box.CopyAbsoluteBoneTransformsTo(transforms);

            // Draw the model. A model can have multiple meshes, so loop.
            foreach (ModelMesh mesh in box.Meshes)
            {
                // This is where the mesh orientation is set, as well 
                // as our camera and projection.
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();
                    effect.World = transforms[mesh.ParentBone.Index] *
                        animateCube.getWorld * Matrix.CreateTranslation(new Vector3(0, -0.5f, 0.5f));
                    effect.View = View;
                    
                    effect.Projection = Projection;
                }
                // Draw the mesh, using the effects set above.
                mesh.Draw();
            }

            spriteBatch.Begin();

            foreach (var joint in kinect.getRightHandJointCollection.getJointsCollection())
            {
                spriteBatch.Draw(dot, helperFunctions.screenizeKinectPosition(joint.Position, this, 300), Color.White);
            }

            foreach (var joint in kinect.getLeftHandJointCollection.getJointsCollection())
            {
                spriteBatch.Draw(dot, helperFunctions.screenizeKinectPosition(joint.Position, this, 300), Color.White);
            }

            foreach (var item in animatedTextAlerts)
            {
                item.Draw(spriteBatch, bigFont, new Vector2(400, 240));
            }

            spriteBatch.DrawString(font, "LeftHAND", new Vector2(kinect.leftHandScreenPosition.X, kinect.leftHandScreenPosition.Y), leftHandColor);
            spriteBatch.DrawString(font, "RightHAND", new Vector2(kinect.rightHandScreenPosition.X, kinect.rightHandScreenPosition.Y), rightHandColor);
            spriteBatch.DrawString(font, "HEAD", new Vector2(kinect.headScreenPosition.X, kinect.headScreenPosition.Y), headColor);
            spriteBatch.DrawString(font, string.Format("DeltaRotationX: {0}", deltaRotationX), new Vector2(6f, 36f), Color.Yellow);
            spriteBatch.DrawString(font, string.Format("DeltaRotationY: {0}", deltaRotationY), new Vector2(6f, 46f), Color.Yellow);
            spriteBatch.DrawString(font, string.Format("DeltaRotationZ: {0}", deltaRotationZ), new Vector2(6f, 56f), Color.Yellow);
            spriteBatch.DrawString(font, string.Format("LeftHand: X:{0} Y:{1} Z{2}", kinect.leftHandWorldPosition.X, kinect.leftHandWorldPosition.Y, kinect.leftHandWorldPosition.Z), new Vector2(6f, 66f), Color.Red);
            spriteBatch.DrawString(font, string.Format("RightHand: X:{0} Y:{1} Z{2}", kinect.rightHandWorldPosition.X, kinect.rightHandWorldPosition.Y, kinect.rightHandWorldPosition.Z), new Vector2(6f, 76f), Color.Red);
            spriteBatch.End();

            base.Draw(gameTime);
        }

        private void resetState()
        {
            GraphicsDevice.RasterizerState = rasterizerState;
            GraphicsDevice.BlendState = blendState;
            GraphicsDevice.DepthStencilState = deptStencilState;
        }

        #endregion

        #region Manage KinectController Events

        void kinect_gestureDetected(object sender, IGesture gesture)
        {
            if (gesture is rightHandSwipeRight)
            {
                textAlertsToAdd.Add("RightHandSwipeRight!!");
                animateCube.setAnimation(Vector3.Up, MathHelper.ToRadians(90));
            }
            else if (gesture is rightHandSwipeLeft)
            {
                textAlertsToAdd.Add("RightHandSwipeLeft!!");
                animateCube.setAnimation(Vector3.Up, -MathHelper.ToRadians(90));
            }
            else if (gesture is rightHandSwipeUp)
            {
                textAlertsToAdd.Add("RightHandSwipeUp!!");
                animateCube.setAnimation(Vector3.Left, MathHelper.ToRadians(90));
            }
            else if (gesture is rightHandSwipeDown)
            {
                textAlertsToAdd.Add("RightHandSwipeDown!!");
                animateCube.setAnimation(Vector3.Left, -MathHelper.ToRadians(90));
            }
        }

        #endregion

        #region Helper functions

        private void controlHandsZ()
        {
            if (Math.Abs(kinect.rightHandWorldPosition.Z - kinect.headWorldPosition.Z) > 0.3f)
            {
                rightHandYEAH = true;
                rightHandColor = Color.Red;
            }
            else
            {
                rightHandYEAH = false;
                rightHandColor = Color.White;
            }

            if (Math.Abs(kinect.leftHandWorldPosition.Z - kinect.headWorldPosition.Z) > 0.3f)
            {
                leftHandYEAH = true;
                leftHandColor = Color.Red;
            }
            else
            {
                leftHandYEAH = false;
                leftHandColor = Color.White;
            }
        }

        #endregion

    }

    #region Static Class for Cube Animation

    static class animateCube
    {
        static Matrix world = Matrix.Identity, animatingWorld;
        static float destinationAngle;
        static Vector3 rotatingAxes;
        static bool animationIsActive = false;
        static long animationStart;
        static long duration = TimeSpan.FromSeconds(0.5f).Ticks;

        public static Matrix getWorld { get { if (animationIsActive) return animatingWorld; else return world; } }

        public static void setAnimation(Vector3 axes, float rotation)
        { 
            destinationAngle = rotation;
            rotatingAxes = axes;
            animationIsActive = true;
            animationStart = DateTime.Now.Ticks;
            animatingWorld = world;
        }

        public static void Update()
        {
            long now = DateTime.Now.Ticks;
            if (now > animationStart + duration && animationIsActive)
            {
                world = animatingWorld;
                animationIsActive = false;
            }
            else if (animationIsActive)
            {
                float value = ((float)(now - animationStart) / duration) * destinationAngle;
                Matrix temp;
                Matrix.CreateFromAxisAngle(ref rotatingAxes, value, out temp);
                animatingWorld = world * temp;
            }
        }
    }

    #endregion

}
